//
//  KoiSyncDataBase.swift
//  KoiAccounting
//
//  Created by 纪洪文 on 18.04.25.
//

import Foundation
protocol KoiSyncDataBaseProtocol {
    var syncPref: String { get }
}

class KoiDataSyncBase<Data: Codable> : KoiSyncDataBaseProtocol{
    
    private let PAGE_SIZE = 500
    private let userDefaults = UserDefaults.standard
    
    var syncPref: String {
        fatalError("syncPref not impl")
    }
    
    var uid: String  {
        return KoiAccountingUserManager.standard.userId()
    }
    
    var bookId: String  {
        return BookManager.standard.currentBookId()
    }
    
    func sync() -> Bool {
        push()
        return pull()
    }
    
    func pull(offset: Int? = nil) -> Bool {
        logger.info("sync pull begin")
        return (try? processPullResponse(result: getPullResponse(offset: offset))) == true
    }
    
    func push(offset: Int = 0) {
        logger.info("sync push begin")
        try? processPushRequest(pushData: getPushData(pageSize: PAGE_SIZE, offset: offset), offset: offset)
    }
    
    func getPullResponse(offset: Int? = nil) -> Result<KoiSyncResponseData<Data>?, KoiNetError> {
        fatalError("getPullResponse not impl")
    }
    
    func getPushResponse(list: Array<Data>, offset: Int) -> Result<KoiSyncResponseData<Data>?, KoiNetError> {
        fatalError("getPushResponse not impl")
    }
    
    func getPushData(pageSize:Int, offset:Int) -> Array<Data> {
        fatalError("getPushData not impl")
    }
    
    func updatePullData(list: Array<Data>, lastSv: Int) throws {
        fatalError("updatePullData not impl")
    }
    
    func updatePushData(list: Array<Data>?, lastSv: Int) throws {
        fatalError("updatePushData not impl")
    }

    func getLastSv() -> Int {
        return userDefaults.integer(forKey: syncPref+"last_sv:"+uid)
    }
    
    func saveLastSv(sv:Int) {
        userDefaults.set(sv, forKey: syncPref+"last_sv:"+uid)
    }
    
    private func processPullResponse(result: Result<KoiSyncResponseData<Data>?, KoiNetError>) throws -> Bool {
        switch result {
        case .success(let success):
            let offset = success?.offset
            let lastSv = success?.lastSv ?? 0
            if let list = success?.list {
                try updatePullData(list: list, lastSv: lastSv)
            }
            let localLastSv = getLastSv()
            
            if offset != nil && offset != 0 {
                return pull(offset: offset!)
            } else {
                saveLastSv(sv: lastSv)
            }
            return localLastSv != lastSv
        case .failure(let failure):
            logger.error("sync fail \(failure)")
            return false
        }
    }
    
    private func processPushRequest(pushData: Array<Data>, offset: Int) throws {
        var thisOffset = 0
        if pushData.count < offset {
            thisOffset = 0
        } else {
            thisOffset = offset + pushData.count
        }
        let result = getPushResponse(list: pushData, offset: thisOffset)
        switch result {
        case .success(let success):
            let lastSv = success?.lastSv ?? 0
            let respOffset = success?.offset
            try updatePushData(list: success?.list, lastSv: lastSv)
            if respOffset == nil || respOffset == 0 {
                logger.debug("push sync done")
            } else {
                push(offset: respOffset!)
            }
        case .failure(let failure):
            logger.error("processPushRequest fail \(failure)")
            throw failure
        }
    }
}
